Interacting with Processes Under the .NET Platform

Although processes and threads are nothing new, the manner in which you interact with these - primitives under the .NET platform has changed quite a bit (for the better). To pave the way to understanding the world of building multithreaded assemblies (see Chapter 19), let’s begin by checking out how to interact with processes using the .NET base class libraries.

The System.Diagnostics namespace defines a number of types that allow you to programmatically interact with processes and various diagnostic-related types such as the system event log and performance counters. In this chapter, you are only concerned with the process-centric types defined in Table 16-1.

Table 16-1. Select Members of the System.Diagnostics Namespace

Process-Centric Types of the System.Diagnostics Namespace Meaning in Life
Process The Process class provides access to local and remote processes and also allows you to programmatically start and stop processes.
ProcessModule This type represents a module (*.dll or *.exe) that is loaded into a particular process. Understand that the ProcessModule type can represent any module—COM-based, .NET-based, or traditional C-based binaries.
ProcessModuleCollection Provides a strongly typed collection of ProcessModule objects.
ProcessStartInfo Specifies a set of values used when starting a process via the Process.Start() method.
ProcessThread Represents a thread within a given process. Be aware that ProcessThread is a type used to diagnose a process’s thread set and is not used to spawn new threads of execution within a process.
ProcessThreadCollection Provides a strongly typed collection of ProcessThread objects.

The System.Diagnostics.Process class allows you to analyze the processes running on a given machine (local or remote). The Process class also provides members that allow you to programmatically start and terminate processes, view (or modify) a process’s priority level, and obtain a list of active threads and/or loaded modules within a given process. Table 16-2 lists some of the key properties of System.Diagnostics.Process.

Table 16-2. Select Properties of the Process Type

Property Meaning in Life
ExitTime This property gets the timestamp associated with the process that has terminated (represented with a DateTime type).
Handle This property returns the handle (represented by an IntPtr) associated to the process by the OS. This can be useful when working building .NET applications which need to communicate with unmanaged code.
Id This property gets the PID for the associated process.
MachineName This property gets the name of the computer the associated process is running on.
MainWindowTitle MainWindowTitle gets the caption of the main window of the process (if the process does not have a main window, you receive an empty string).
Modules This property provides access to the strongly typed ProcessModuleCollection type, which represents the set of modules (*.dll or *.exe) loaded within the current process.
ProcessName This property gets the name of the process (which, as you would assume, is the name of the application itself).
Responding This property gets a value indicating whether the user interface of the process is responding to user input (or is currently “hung”).
StartTime This property gets the time that the associated process was started (via a DateTime type).
Threads This property gets the set of threads that are running in the associated process (represented via a collection of ProcessThread objects).

In addition to the properties just examined, System.Diagnostics.Process also defines a few useful methods (Table 16-3).

Table 16-3. Select Methods of the Process Type

Method Meaning in Life
CloseMainWindow() This method closes a process that has a user interface by sending a close message to its main window.
GetCurrentProcess() This static method returns a new Process object that represents the currently active process.
GetProcesses() This static method returns an array of new Process objects running on a given machine.
Kill() This method immediately stops the associated process.
Start() This method starts a process.

Enumerating Running Processes

To illustrate the process of manipulating Process objects (pardon the redundancy), assume you have a C# Console Application named ProcessManipulator that defines the following static helper method within the Program class (be sure you import the System.Diagnostics namespace in your code file):

static void ListAllRunningProcesses()
{
    // Get all the processes on the local machine, ordered by
    // PID.
    var runningProcs =
        from proc in Process.GetProcesses(".") orderby proc.Id select proc;

    // Print out PID and name of each process.
    foreach(var p in runningProcs)
    {
        string info = string.Format("-> PID: {0}\tName: {1}",
            p.Id, p.ProcessName);
        Console.WriteLine(info);
    }
    Console.WriteLine("************************************\n");
}

The static Process.GetProcesses() method returns an array of Process objects that represent the running processes on the target machine (the dot notation shown here represents the local computer). Once you have obtained the array of Process objects, you are able to invoke any of the members seen in Table 16-2 and Table 16-3. Here, you are simply displaying the PID and the name of each process, ordered by PID. Assuming the Main() method has been updated to call ListAllRunningProcesses():

static void Main(string[] args)
{
    Console.WriteLine("***** Fun with Processes *****\n");
    ListAllRunningProcesses();
    Console.ReadLine();
}

you will see the names and PIDs for all processes on your local computer. Here is some partial output from my current machine.

***** Fun with Processes *****

-> PID: 0     Name: Idle
-> PID: 4     Name: System
-> PID: 108     Name: iexplore
-> PID: 268     Name: smss
-> PID: 432     Name: csrss
-> PID: 448     Name: svchost
-> PID: 472     Name: wininit
-> PID: 504     Name: csrss
-> PID: 536     Name: winlogon
-> PID: 560     Name: services
-> PID: 584     Name: lsass
-> PID: 592     Name: lsm
-> PID: 660    Name: devenv
-> PID: 684     Name: svchost
-> PID: 760     Name: svchost
-> PID: 832     Name: svchost
-> PID: 844     Name: svchost
-> PID: 856     Name: svchost
-> PID: 900     Name: svchost
-> PID: 924     Name: svchost
-> PID: 956     Name: VMwareService
-> PID: 1116     Name: spoolsv
-> PID: 1136     Name: ProcessManipulator.vshost
************************************

Investigating a Specific Process

In addition to obtaining a full and complete list of all running processes on a given machine, the static Process.GetProcessById() method allows you to obtain a single Process object via the associated PID. If you request access to a nonexistent PID, an ArgumentException exception is thrown. For example, if you were interested in obtaining a Process object representing a process with the PID of 987, you could write the following code:

// If there is no process with the PID of 987, a
// runtime exception will be thrown.
static void GetSpecificProcess()
{
    Process theProc = null;
    try
    {
        theProc = Process.GetProcessById(987);
    }
    catch(ArgumentException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

At this point, you have learned how to get a list of all processes, or a specific process on a machine via a PID lookup. While it is somewhat useful to discover PIDs and process names, the Process class also allows you to discover the set of current threads and libraries used within a given process. Let’s see how to do so.

Investigating a Process’s Thread Set

The set of threads is represented by the strongly typed ProcessThreadCollection collection, which contains some number of individual ProcessThread objects. To illustrate, assume the following additional static helper function has been added to your current application:

static void EnumThreadsForPid(int pID)
{
    Process theProc = null;
    try
    {
        theProc = Process.GetProcessById(pID);
    }
    catch(ArgumentException ex)
    {
        Console.WriteLine(ex.Message);
        return;
    }

    // List out stats for each thread in the specified process.
    Console.WriteLine("Here are the threads used by: {0}",
        theProc.ProcessName);
    ProcessThreadCollection theThreads = theProc.Threads;

    foreach(ProcessThread pt in theThreads)
    {
        string info =
            string.Format("-> Thread ID: {0}\tStart Time: {1}\tPriority: {2}",
                pt.Id , pt.StartTime.ToShortTimeString(), pt.PriorityLevel);
        Console.WriteLine(info);
    }
    Console.WriteLine("************************************\n");
}

As you can see, the Threads property of the System.Diagnostics.Process type provides access to the ProcessThreadCollection class. Here, you are printing out the assigned thread ID, start time, and priority level of each thread in the process specified by the client. Now, update your program’s Main() method to prompt the user for a PID to investigate, as follows:

static void Main(string[] args)
{
...
    // Prompt user for a PID and print out the set of active threads.
    Console.WriteLine("***** Enter PID of process to investigate *****");
    Console.Write("PID: ");
    string pID = Console.ReadLine();
    int theProcID = int.Parse(pID);
    
    EnumThreadsForPid(theProcID);
    Console.ReadLine();
}

When you run your program, you can now enter the PID of any process on your machine, and see the threads used in the process. The following output shows the threads used by PID 108 on my machine, which happens to be hosting Microsoft Internet Explorer:

***** Enter PID of process to investigate *****
PID: 108
Here are the threads used by: iexplore
-> Thread ID: 680     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 2040     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 880     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 3380     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 3376     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 3448     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 3476     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 2264     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 2380     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 2384     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 2308     Start Time: 9:05 AM     Priority: Normal
-> Thread ID: 3096     Start Time: 9:07 AM     Priority: Highest
-> Thread ID: 3600     Start Time: 9:45 AM     Priority: Normal
-> Thread ID: 1412     Start Time: 10:02 AM     Priority: Normal

The ProcessThread type has additional members of interest beyond Id, StartTime, and PriorityLevel. Table 16-4 documents some members of interest.

Table 16-4. Select Members of the ProcessThread Type

Member Meaning in Life
CurrentPriority Gets the current priority of the thread
Id Gets the unique identifier of the thread
IdealProcessor Sets the preferred processor for this thread to run on
PriorityLevel Gets or sets the priority level of the thread
ProcessorAffinity Sets the processors on which the associated thread can run
StartAddress Gets the memory address of the function that the operating system called that started this thread
StartTime Gets the time that the operating system started the thread
ThreadState Gets the current state of this thread
TotalProcessorTime Gets the total amount of time that this thread has spent using the processor
WaitReason Gets the reason that the thread is waiting

Before you read any further, be very aware that the ProcessThread type is not the entity used to create, suspend, or kill threads under the .NET platform. Rather, ProcessThread is a vehicle used to obtain diagnostic information for the active Windows threads within a running process. Again, you will investigate how to build multithreaded applications using the System.Threading namespace in Chapter 19.

Investigating a Process’s Module Set

Next up, let’s check out how to iterate over the number of loaded modules that are hosted within a given process. When talking about processes, a module is a general term used to describe a given *.dll (or the *.exe itself) that is hosted by a specific process. When you access the ProcessModuleCollection via the Process.Modules property, you are able to enumerate over all modules hosted within a process: .NETbased, COM-based, or traditional C-based libraries. Ponder the following additional helper function that will enumerate the modules in a specific process based on the PID:

static void EnumModsForPid(int pID)
{
    Process theProc = null;
    try
    {
        theProc = Process.GetProcessById(pID);
    }
    catch(ArgumentException ex)
    {
        Console.WriteLine(ex.Message);
        return;
    }

    Console.WriteLine("Here are the loaded modules for: {0}",
        theProc.ProcessName);
    ProcessModuleCollection theMods = theProc.Modules;
    foreach(ProcessModule pm in theMods)
    {
        string info = string.Format("-> Mod Name: {0}", pm.ModuleName);
        Console.WriteLine(info);
    }
    Console.WriteLine("************************************\n");
}

To see some possible output, let’s check out the loaded modules for the process hosting the current example program (ProcessManipulator). To do so, run the application, identify the PID assigned to ProcessManipulator.exe (via the Task Manager) and pass this value to the EnumModsForPid() method (be sure to update your Main() method accordingly). Once you do, you may be surprised to see the list of *.dlls used for a simple Console Application (GDI32.dll, USER32.dll, ole32.dll, and so forth). Consider the following output:

Here are the loaded modules for: ProcessManipulator
-> Mod Name: ProcessManipulator.exe
-> Mod Name: ntdll.dll
-> Mod Name: MSCOREE.DLL
-> Mod Name: KERNEL32.dll
-> Mod Name: KERNELBASE.dll
-> Mod Name: ADVAPI32.dll
-> Mod Name: msvcrt.dll
-> Mod Name: sechost.dll
-> Mod Name: RPCRT4.dll
-> Mod Name: SspiCli.dll
-> Mod Name: CRYPTBASE.dll
-> Mod Name: mscoreei.dll
-> Mod Name: SHLWAPI.dll
-> Mod Name: GDI32.dll
-> Mod Name: USER32.dll
-> Mod Name: LPK.dll
-> Mod Name: USP10.dll
-> Mod Name: IMM32.DLL
-> Mod Name: MSCTF.dll
-> Mod Name: clr.dll
-> Mod Name: MSVCR100_CLR0400.dll
-> Mod Name: mscorlib.ni.dll
-> Mod Name: nlssorting.dll
-> Mod Name: ole32.dll
-> Mod Name: clrjit.dll
-> Mod Name: System.ni.dll
-> Mod Name: System.Core.ni.dll
-> Mod Name: psapi.dll
-> Mod Name: shfolder.dll
-> Mod Name: SHELL32.dll
************************************

Starting and Stopping Processes Programmatically

The final aspects of the System.Diagnostics.Process class examined here are the Start() and Kill() methods. As you can gather by their names, these members provide a way to programmatically launch and terminate a process, respectively. For example, consider the static StartAndKillProcess() helper method:

Note You must be running Visual Studio 2010 with Administrator rights in order to start new processes. If this is not the case, you will receive a runtime error.

static void StartAndKillProcess()
{
    Process ieProc = null;

    // Launch Internet Explorer, and go to facebook!
    try
    {
        ieProc = Process.Start("IExplore.exe", "www.facebook.com");
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.Message);
    }

    Console.Write("--> Hit enter to kill {0}...", ieProc.ProcessName);
    Console.ReadLine();

    // Kill the iexplore.exe process.
    try
    {
        ieProc.Kill();
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

The static Process.Start() method has been overloaded a few times. At minimum, you will need to specify the friendly name of the process you wish to launch (such as Microsoft Internet Explorer, iexplore.exe). This example makes use of a variation of the Start() method that allows you to specify any additional arguments to pass into the program’s entry point (i.e., the Main() method).

Once you call the Start() method, you are returned a reference to the newly activated process. When you wish to terminate the process, simply call the instance-level Kill() method. Here, you are wrapping the calls to Start() and Kill() within a try / catch block, and handling any InvalidOperationException errors. This is especially important when calling the Kill() method, as this error will be raised if the process has already been terminated prior to calling Kill().

Controlling Process Startup using the ProcessStartInfo Class

The Start() method also allows you to pass in a System.Diagnostics.ProcessStartInfo type to specify additional bits of information regarding how a given process should come to life. Here is a partial definition of ProcessStartInfo (see the .NET Framework 4.0 SDK documentation for full details):

public sealed class ProcessStartInfo : object
{
    public ProcessStartInfo();
    public ProcessStartInfo(string fileName);
    public ProcessStartInfo(string fileName, string arguments);
    public string Arguments { get; set; }
    public bool CreateNoWindow { get; set; }
    public StringDictionary EnvironmentVariables { get; }
    public bool ErrorDialog { get; set; }
    public IntPtr ErrorDialogParentHandle { get; set; }
    public string FileName { get; set; }
    public bool LoadUserProfile { get; set; }
    public SecureString Password { get; set; }
    public bool RedirectStandardError { get; set; }
    public bool RedirectStandardInput { get; set; }
    public bool RedirectStandardOutput { get; set; }
    public Encoding StandardErrorEncoding { get; set; }
    public Encoding StandardOutputEncoding { get; set; }
    public bool UseShellExecute { get; set; }
    public string Verb { get; set; }
    public string[] Verbs { get; }
    public ProcessWindowStyle WindowStyle { get; set; }
    public string WorkingDirectory { get; set; }
}

To illustrate how to fine-tune your process startup, here is a modified version of StartAndKillProcess(), which will load Microsoft Internet Explorer, navigate to www.facebook.com, and show the window in a maximized state:

static void StartAndKillProcess()
{
    Process ieProc = null;

    // Launch Internet Explorer, and go to facebook,
    // with maximized window.
    try
    {
        ProcessStartInfo startInfo = new
            ProcessStartInfo("IExplore.exe", "www.facebook.com");
        startInfo.WindowStyle = ProcessWindowStyle.Maximized;

        ieProc = Process.Start(startInfo);
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex.Message);
    }
...
}

Great! Now that you understand the role of Windows processes and how to interact with them from C# code, you are ready to investigate the concept of a .NET application domain.

Source Code The ProcessManipulator project is included under the Chapter 16 subdirectory.